Odkryj bezpieczn膮 komunikacj臋 mi臋dzy藕r贸d艂ow膮 za pomoc膮 PostMessage API. Poznaj jego mo偶liwo艣ci, zagro偶enia bezpiecze艅stwa i najlepsze praktyki w celu 艂agodzenia podatno艣ci w aplikacjach internetowych.
Komunikacja mi臋dzy藕r贸d艂owa: Wzorce bezpiecze艅stwa z PostMessage API
We wsp贸艂czesnym internecie aplikacje cz臋sto musz膮 wchodzi膰 w interakcje z zasobami z r贸偶nych 藕r贸de艂. Same-Origin Policy (SOP) to kluczowy mechanizm bezpiecze艅stwa, kt贸ry ogranicza skryptom dost臋p do zasob贸w z innego 藕r贸d艂a. Istniej膮 jednak uzasadnione scenariusze, w kt贸rych komunikacja mi臋dzy藕r贸d艂owa jest konieczna. API postMessage zapewnia kontrolowany mechanizm do osi膮gni臋cia tego celu, ale kluczowe jest zrozumienie jego potencjalnych zagro偶e艅 bezpiecze艅stwa i wdro偶enie odpowiednich wzorc贸w bezpiecze艅stwa.
Zrozumienie Same-Origin Policy (SOP)
Same-Origin Policy to fundamentalna koncepcja bezpiecze艅stwa w przegl膮darkach internetowych. Ogranicza ona strony internetowe przed wysy艂aniem 偶膮da艅 do domeny innej ni偶 ta, kt贸ra dostarczy艂a stron臋. 殴r贸d艂o (origin) jest definiowane przez schemat (protok贸艂), hosta (domen臋) i port. Je艣li kt贸rykolwiek z tych element贸w si臋 r贸偶ni, 藕r贸d艂a s膮 uwa偶ane za r贸偶ne. Na przyk艂ad:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Wszystkie te adresy s膮 r贸偶nymi 藕r贸d艂ami, a SOP ogranicza bezpo艣redni dost臋p skrypt贸w mi臋dzy nimi.
Wprowadzenie do PostMessage API
API postMessage zapewnia bezpieczny i kontrolowany mechanizm komunikacji mi臋dzy藕r贸d艂owej. Pozwala skryptom na wysy艂anie wiadomo艣ci do innych okien (np. ramek iframe, nowych okien lub kart), niezale偶nie od ich 藕r贸d艂a. Okno odbieraj膮ce mo偶e nast臋pnie nas艂uchiwa膰 na te wiadomo艣ci i odpowiednio je przetwarza膰.
Podstawowa sk艂adnia wysy艂ania wiadomo艣ci to:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Odniesienie do okna docelowego (np.window.parent,iframe.contentWindowlub obiekt okna uzyskany zwindow.open).message: Dane, kt贸re chcesz wys艂a膰. Mo偶e to by膰 dowolny obiekt JavaScript, kt贸ry mo偶na serializowa膰 (np. ci膮gi znak贸w, liczby, obiekty, tablice).targetOrigin: Okre艣la 藕r贸d艂o, do kt贸rego chcesz wys艂a膰 wiadomo艣膰. Jest to kluczowy parametr bezpiecze艅stwa.
Po stronie odbiorczej nale偶y nas艂uchiwa膰 na zdarzenie message:
window.addEventListener('message', function(event) {
// ...
});
Obiekt event zawiera nast臋puj膮ce w艂a艣ciwo艣ci:
event.data: Wiadomo艣膰 wys艂ana przez drugie okno.event.origin: 殴r贸d艂o okna, kt贸re wys艂a艂o wiadomo艣膰.event.source: Odniesienie do okna, kt贸re wys艂a艂o wiadomo艣膰.
Zagro偶enia i podatno艣ci bezpiecze艅stwa
Chocia偶 postMessage oferuje spos贸b na obej艣cie ogranicze艅 SOP, wprowadza r贸wnie偶 potencjalne zagro偶enia bezpiecze艅stwa, je艣li nie jest starannie zaimplementowane. Oto kilka typowych podatno艣ci:
1. Niezgodno艣膰 docelowego 藕r贸d艂a (Target Origin)
Brak walidacji w艂a艣ciwo艣ci event.origin jest krytyczn膮 podatno艣ci膮. Je艣li odbiorca 艣lepo ufa wiadomo艣ci, ka偶da strona internetowa mo偶e wys艂a膰 z艂o艣liwe dane. Zawsze weryfikuj, czy event.origin pasuje do oczekiwanego 藕r贸d艂a przed przetworzeniem wiadomo艣ci.
Przyk艂ad (Podatny kod):
window.addEventListener('message', function(event) {
// NIE R脫B TEGO!
processMessage(event.data);
});
Przyk艂ad (Bezpieczny kod):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Received message from untrusted origin:', event.origin);
return;
}
processMessage(event.data);
});
2. Wstrzykiwanie danych (Data Injection)
Traktowanie otrzymanych danych (event.data) jako kodu wykonywalnego lub bezpo艣rednie wstrzykiwanie ich do DOM mo偶e prowadzi膰 do podatno艣ci Cross-Site Scripting (XSS). Zawsze odka偶aj (sanitise) i waliduj otrzymane dane przed ich u偶yciem.
Przyk艂ad (Podatny kod):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // NIE R脫B TEGO!
}
});
Przyk艂ad (Bezpieczny kod):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Zaimplementuj odpowiedni膮 funkcj臋 odka偶ania
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Zaimplementuj tutaj solidn膮 logik臋 odka偶ania.
// Na przyk艂ad, u偶yj DOMPurify lub podobnej biblioteki
return DOMPurify.sanitize(data);
}
3. Ataki typu Man-in-the-Middle (MITM)
Je艣li komunikacja odbywa si臋 przez niezabezpieczony kana艂 (HTTP), atakuj膮cy MITM mo偶e przechwyci膰 i zmodyfikowa膰 wiadomo艣ci. Zawsze u偶ywaj HTTPS do bezpiecznej komunikacji.
4. Cross-Site Request Forgery (CSRF)
Je艣li odbiorca wykonuje dzia艂ania na podstawie otrzymanej wiadomo艣ci bez odpowiedniej walidacji, atakuj膮cy m贸g艂by potencjalnie sfa艂szowa膰 wiadomo艣ci, aby sk艂oni膰 odbiorc臋 do wykonania niezamierzonych dzia艂a艅. Wdr贸偶 mechanizmy ochrony przed CSRF, takie jak do艂膮czenie tajnego tokenu do wiadomo艣ci i weryfikacja go po stronie odbiorcy.
5. U偶ywanie symboli wieloznacznych (wildcard) w targetOrigin
Ustawienie targetOrigin na * pozwala ka偶demu 藕r贸d艂u na otrzymanie wiadomo艣ci. Nale偶y tego unika膰, chyba 偶e jest to absolutnie konieczne, poniewa偶 niweczy to cel bezpiecze艅stwa opartego na 藕r贸dle. Je艣li musisz u偶y膰 *, upewnij si臋, 偶e wdro偶y艂e艣 inne silne 艣rodki bezpiecze艅stwa, takie jak kody uwierzytelniania wiadomo艣ci (MAC).
Przyk艂ad (Unikaj tego):
otherWindow.postMessage(message, '*'); // Unikaj u偶ywania '*' chyba 偶e jest to absolutnie konieczne
Wzorce bezpiecze艅stwa i najlepsze praktyki
Aby z艂agodzi膰 ryzyka zwi膮zane z postMessage, post臋puj zgodnie z poni偶szymi wzorcami bezpiecze艅stwa i najlepszymi praktykami:
1. 艢cis艂a walidacja 藕r贸d艂a
Zawsze waliduj w艂a艣ciwo艣膰 event.origin po stronie odbiorcy. Por贸wnuj j膮 z predefiniowan膮 list膮 zaufanych 藕r贸de艂. U偶ywaj 艣cis艂ego por贸wnania (===) do por贸wnania.
2. Odka偶anie (sanitacja) i walidacja danych
Odka偶aj i waliduj wszystkie dane otrzymane przez postMessage przed ich u偶yciem. Stosuj odpowiednie techniki odka偶ania w zale偶no艣ci od tego, jak dane b臋d膮 u偶ywane (np. escapowanie HTML, kodowanie URL, walidacja danych wej艣ciowych). U偶ywaj bibliotek takich jak DOMPurify do odka偶ania HTML.
3. Kody uwierzytelniania wiadomo艣ci (MAC)
Do艂膮cz kod uwierzytelniania wiadomo艣ci (MAC) do wiadomo艣ci, aby zapewni膰 jej integralno艣膰 i autentyczno艣膰. Nadawca oblicza MAC przy u偶yciu wsp贸艂dzielonego tajnego klucza i do艂膮cza go do wiadomo艣ci. Odbiorca ponownie oblicza MAC przy u偶yciu tego samego wsp贸艂dzielonego tajnego klucza i por贸wnuje go z otrzymanym MAC. Je艣li pasuj膮, wiadomo艣膰 jest uwa偶ana za autentyczn膮 i niezmienion膮.
Przyk艂ad (U偶ycie HMAC-SHA256):
// Nadawca
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// Odbiorca
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Received message from untrusted origin:', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Message is authentic!');
processMessage(message); // Kontynuuj przetwarzanie wiadomo艣ci
} else {
console.error('Message signature verification failed!');
}
}
Wa偶ne: Wsp贸艂dzielony tajny klucz musi by膰 bezpiecznie wygenerowany i przechowywany. Unikaj hardkodowania klucza w kodzie.
4. U偶ywanie Nonce i znacznik贸w czasu
Aby zapobiec atakom powt贸rzeniowym (replay attacks), do艂膮cz do wiadomo艣ci unikalny nonce (number used once - liczba u偶yta raz) i znacznik czasu. Odbiorca mo偶e wtedy zweryfikowa膰, czy nonce nie zosta艂 wcze艣niej u偶yty i czy znacznik czasu mie艣ci si臋 w akceptowalnym przedziale czasowym. Zmniejsza to ryzyko, 偶e atakuj膮cy ponownie odtworzy wcze艣niej przechwycone wiadomo艣ci.
5. Zasada najmniejszych uprawnie艅
Nadawaj tylko minimalne niezb臋dne uprawnienia drugiemu oknu. Na przyk艂ad, je艣li drugie okno potrzebuje tylko odczytywa膰 dane, nie pozwalaj mu na ich zapisywanie. Projektuj sw贸j protok贸艂 komunikacyjny z my艣l膮 o zasadzie najmniejszych uprawnie艅.
6. Content Security Policy (CSP)
U偶yj Content Security Policy (CSP), aby ograniczy膰 藕r贸d艂a, z kt贸rych mog膮 by膰 艂adowane skrypty, oraz dzia艂ania, kt贸re skrypty mog膮 wykonywa膰. Mo偶e to pom贸c w z艂agodzeniu skutk贸w podatno艣ci XSS, kt贸re mog膮 wynika膰 z niew艂a艣ciwej obs艂ugi danych postMessage.
7. Walidacja danych wej艣ciowych
Zawsze waliduj struktur臋 i format otrzymanych danych. Zdefiniuj jasny format wiadomo艣ci i upewnij si臋, 偶e otrzymane dane s膮 z nim zgodne. Pomaga to zapobiega膰 nieoczekiwanemu zachowaniu i podatno艣ciom.
8. Bezpieczna serializacja danych
U偶ywaj bezpiecznego formatu serializacji danych, takiego jak JSON, do serializacji i deserializacji wiadomo艣ci. Unikaj u偶ywania format贸w, kt贸re pozwalaj膮 na wykonanie kodu, takich jak eval() lub Function().
9. Ogranicz rozmiar wiadomo艣ci
Ogranicz rozmiar wiadomo艣ci wysy艂anych przez postMessage. Du偶e wiadomo艣ci mog膮 zu偶ywa膰 nadmierne zasoby i potencjalnie prowadzi膰 do atak贸w typu denial-of-service.
10. Regularne audyty bezpiecze艅stwa
Przeprowadzaj regularne audyty bezpiecze艅stwa swojego kodu, aby zidentyfikowa膰 i usun膮膰 potencjalne podatno艣ci. Zwracaj szczeg贸ln膮 uwag臋 na implementacj臋 postMessage i upewnij si臋, 偶e wszystkie najlepsze praktyki bezpiecze艅stwa s膮 przestrzegane.
Przyk艂adowy scenariusz: Bezpieczna komunikacja mi臋dzy ramk膮 Iframe a jej rodzicem
Rozwa偶my scenariusz, w kt贸rym ramka iframe hostowana na https://iframe.example.com musi komunikowa膰 si臋 ze swoj膮 stron膮 nadrz臋dn膮 hostowan膮 na https://parent.example.com. Ramka iframe musi wys艂a膰 dane u偶ytkownika do strony nadrz臋dnej w celu ich przetworzenia.
Iframe (https://iframe.example.com):
// Wygeneruj wsp贸艂dzielony klucz tajny (zast膮p bezpieczn膮 metod膮 generowania klucza)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// Pobierz dane u偶ytkownika
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Wy艣lij dane u偶ytkownika do strony nadrz臋dnej
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
Strona nadrz臋dna (https://parent.example.com):
// Wsp贸艂dzielony klucz tajny (musi pasowa膰 do klucza z ramki iframe)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Received message from untrusted origin:', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Message is authentic!');
// Przetw贸rz dane u偶ytkownika
console.log('User data:', userData);
} else {
console.error('Message signature verification failed!');
}
});
Wa偶ne uwagi:
- Zast膮p
YOUR_SECURE_SHARED_SECRETbezpiecznie wygenerowanym wsp贸艂dzielonym kluczem tajnym. - Wsp贸艂dzielony klucz tajny musi by膰 taki sam zar贸wno w ramce iframe, jak i na stronie nadrz臋dnej.
- Ten przyk艂ad u偶ywa HMAC-SHA256 do uwierzytelniania wiadomo艣ci.
Podsumowanie
API postMessage to pot臋偶ne narz臋dzie umo偶liwiaj膮ce komunikacj臋 mi臋dzy藕r贸d艂ow膮 w aplikacjach internetowych. Jednak偶e, kluczowe jest zrozumienie potencjalnych zagro偶e艅 bezpiecze艅stwa i wdro偶enie odpowiednich wzorc贸w bezpiecze艅stwa w celu ich z艂agodzenia. Post臋puj膮c zgodnie z wzorcami bezpiecze艅stwa i najlepszymi praktykami opisanymi w tym przewodniku, mo偶esz bezpiecznie u偶ywa膰 postMessage do tworzenia solidnych i bezpiecznych aplikacji internetowych.
Pami臋taj, aby zawsze priorytetowo traktowa膰 bezpiecze艅stwo i by膰 na bie偶膮co z najnowszymi najlepszymi praktykami w zakresie tworzenia stron internetowych. Regularnie przegl膮daj sw贸j kod i konfiguracje bezpiecze艅stwa, aby upewni膰 si臋, 偶e Twoje aplikacje s膮 chronione przed potencjalnymi podatno艣ciami.